/* eslint-disable @typescript-eslint/no-explicit-any */
import { Properties as CSSProperties } from 'csstype';
import type { Component } from './component';
import type { JSXElementEventMap, JSXGlobalEventHandlersEventMap } from './eventMap';
export type CSSStyle = CSSProperties | CSSStyleDeclaration;
export type EventMap = JSXElementEventMap & JSXGlobalEventHandlersEventMap;
export type EventKey = keyof EventMap;
export type EventHandler<T extends EventKey, O extends Element> = (this: O, event: EventMap[T]) => void;

export interface BaseElement extends Element, ElementCSSInlineStyle {}
export type ChildType = string | number | boolean | Date | Node | Iterable<ChildType> | undefined;

export type BaseProps = {
	children?: ChildType | ChildType[];
};

export type ObjectProps<T> = BaseProps & {
	key?: string;
	ref?: (elem: T) => void;
};

export type StyledPropsWithoutRef = {
	style?: string | CSSStyle;
	class?: string | string[];
};

export type StyledProps<T> = ObjectProps<T> & StyledPropsWithoutRef;

type ExcludeMethods<T> = {
	// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
	[K in keyof T as T[K] extends Function ? never : K]: T[K] | string;
};

type ExcludeOnPrefix<T> = {
	[K in keyof T as K extends `on${infer _}` ? never : K]: T[K];
};

type EventKeys = {
	[key in keyof EventMap as `on${key}`]: unknown;
};

export type ElementProps<T extends BaseElement> = Record<string, any> &
	StyledProps<T> & {
		[key in keyof EventMap as `on${key}`]?: ((this: T, event: EventMap[key]) => void) | string;
	} & {
		[key in keyof EventMap as `on${key}Capture`]?: (this: T, event: EventMap[key]) => void;
	} & Omit<
		{
			[K in keyof ExcludeOnPrefix<ExcludeMethods<T>>]?: T[K] | string;
		},
		'children' | 'style' | keyof EventKeys
	>;

export type ElementPropsWithoutRef<T extends BaseElement> = StyledPropsWithoutRef &
	Omit<
		{
			[K in keyof ExcludeOnPrefix<ExcludeMethods<T>>]?: T[K] | string;
		},
		'children' | 'style' | keyof EventKeys
	>;

export type ComponentProps<
	T extends Component<any>,
	ExtendElement extends BaseElement | unknown = unknown,
> = ExtendElement extends BaseElement
	? ElementPropsWithoutRef<ExtendElement> & { ref?: (elem: T) => void }
	: ObjectProps<T>;

export type JSXFunction = (
	props: Record<string, any> & { children?: ChildType | ChildType[] }
) => BaseElement | DocumentFragment;

export interface JSXClass<T> {
	new (props: ObjectProps<T>): this;
	get el(): BaseElement;
}

declare global {
	// eslint-disable-next-line @typescript-eslint/no-namespace
	namespace JSX {
		interface HTMLIntrinsicElements {
			a: ElementProps<HTMLAnchorElement>;
			abbr: ElementProps<HTMLElement>;
			address: ElementProps<HTMLElement>;
			area: ElementProps<HTMLAreaElement>;
			article: ElementProps<HTMLElement>;
			aside: ElementProps<HTMLElement>;
			audio: ElementProps<HTMLAudioElement>;
			b: ElementProps<HTMLElement>;
			base: ElementProps<HTMLBaseElement>;
			bdi: ElementProps<HTMLElement>;
			bdo: ElementProps<HTMLElement>;
			blockquote: ElementProps<HTMLQuoteElement>;
			body: ElementProps<HTMLBodyElement>;
			br: ElementProps<HTMLBRElement>;
			button: ElementProps<HTMLButtonElement>;
			canvas: ElementProps<HTMLCanvasElement>;
			caption: ElementProps<HTMLTableCaptionElement>;
			cite: ElementProps<HTMLElement>;
			code: ElementProps<HTMLElement>;
			col: ElementProps<HTMLTableColElement>;
			colgroup: ElementProps<HTMLTableColElement>;
			data: ElementProps<HTMLDataElement>;
			datalist: ElementProps<HTMLDataListElement>;
			dd: ElementProps<HTMLElement>;
			del: ElementProps<HTMLModElement>;
			details: ElementProps<HTMLDetailsElement>;
			dfn: ElementProps<HTMLElement>;
			dialog: ElementProps<HTMLDialogElement>;
			div: ElementProps<HTMLDivElement>;
			dl: ElementProps<HTMLDListElement>;
			dt: ElementProps<HTMLElement>;
			em: ElementProps<HTMLElement>;
			embed: ElementProps<HTMLEmbedElement>;
			fieldset: ElementProps<HTMLFieldSetElement>;
			figcaption: ElementProps<HTMLElement>;
			figure: ElementProps<HTMLElement>;
			footer: ElementProps<HTMLElement>;
			form: ElementProps<HTMLFormElement>;
			h1: ElementProps<HTMLHeadingElement>;
			h2: ElementProps<HTMLHeadingElement>;
			h3: ElementProps<HTMLHeadingElement>;
			h4: ElementProps<HTMLHeadingElement>;
			h5: ElementProps<HTMLHeadingElement>;
			h6: ElementProps<HTMLHeadingElement>;
			head: ElementProps<HTMLHeadElement>;
			header: ElementProps<HTMLElement>;
			hgroup: ElementProps<HTMLElement>;
			hr: ElementProps<HTMLHRElement>;
			html: ElementProps<HTMLHtmlElement>;
			i: ElementProps<HTMLElement>;
			iframe: ElementProps<HTMLIFrameElement>;
			img: ElementProps<HTMLImageElement>;
			input: ElementProps<HTMLInputElement>;
			ins: ElementProps<HTMLModElement>;
			kbd: ElementProps<HTMLElement>;
			label: ElementProps<HTMLLabelElement>;
			legend: ElementProps<HTMLLegendElement>;
			li: ElementProps<HTMLLIElement>;
			link: ElementProps<HTMLLinkElement>;
			main: ElementProps<HTMLElement>;
			map: ElementProps<HTMLMapElement>;
			mark: ElementProps<HTMLElement>;
			menu: ElementProps<HTMLMenuElement>;
			meta: ElementProps<HTMLMetaElement>;
			meter: ElementProps<HTMLMeterElement>;
			nav: ElementProps<HTMLElement>;
			noscript: ElementProps<HTMLElement>;
			object: ElementProps<HTMLObjectElement>;
			ol: ElementProps<HTMLOListElement>;
			optgroup: ElementProps<HTMLOptGroupElement>;
			option: ElementProps<HTMLOptionElement>;
			output: ElementProps<HTMLOutputElement>;
			p: ElementProps<HTMLParagraphElement>;
			picture: ElementProps<HTMLPictureElement>;
			pre: ElementProps<HTMLPreElement>;
			progress: ElementProps<HTMLProgressElement>;
			q: ElementProps<HTMLQuoteElement>;
			rp: ElementProps<HTMLElement>;
			rt: ElementProps<HTMLElement>;
			ruby: ElementProps<HTMLElement>;
			s: ElementProps<HTMLElement>;
			samp: ElementProps<HTMLElement>;
			script: ElementProps<HTMLScriptElement>;
			section: ElementProps<HTMLElement>;
			select: ElementProps<HTMLSelectElement>;
			slot: ElementProps<HTMLSlotElement>;
			small: ElementProps<HTMLElement>;
			source: ElementProps<HTMLSourceElement>;
			span: ElementProps<HTMLSpanElement>;
			strong: ElementProps<HTMLElement>;
			style: ElementProps<HTMLStyleElement>;
			sub: ElementProps<HTMLElement>;
			summary: ElementProps<HTMLElement>;
			sup: ElementProps<HTMLElement>;
			table: ElementProps<HTMLTableElement>;
			tbody: ElementProps<HTMLTableSectionElement>;
			td: ElementProps<HTMLTableCellElement>;
			template: ElementProps<HTMLTemplateElement>;
			textarea: ElementProps<HTMLTextAreaElement>;
			tfoot: ElementProps<HTMLTableSectionElement>;
			th: ElementProps<HTMLTableCellElement>;
			thead: ElementProps<HTMLTableSectionElement>;
			time: ElementProps<HTMLTimeElement>;
			title: ElementProps<HTMLTitleElement>;
			tr: ElementProps<HTMLTableRowElement>;
			track: ElementProps<HTMLTrackElement>;
			u: ElementProps<HTMLElement>;
			ul: ElementProps<HTMLUListElement>;
			var: ElementProps<HTMLElement>;
			video: ElementProps<HTMLVideoElement>;
			wbr: ElementProps<HTMLElement>;
		}
		interface SVGIntrinsicElements {
			'svg:a': ElementProps<SVGAElement>;
			animate: ElementProps<SVGAnimateElement>;
			animateMotion: ElementProps<SVGAnimateMotionElement>;
			animateTransform: ElementProps<SVGAnimateTransformElement>;
			circle: ElementProps<SVGCircleElement>;
			clipPath: ElementProps<SVGClipPathElement>;
			defs: ElementProps<SVGDefsElement>;
			desc: ElementProps<SVGDescElement>;
			ellipse: ElementProps<SVGEllipseElement>;
			feBlend: ElementProps<SVGFEBlendElement>;
			feColorMatrix: ElementProps<SVGFEColorMatrixElement>;
			feComponentTransfer: ElementProps<SVGFEComponentTransferElement>;
			feComposite: ElementProps<SVGFECompositeElement>;
			feConvolveMatrix: ElementProps<SVGFEConvolveMatrixElement>;
			feDiffuseLighting: ElementProps<SVGFEDiffuseLightingElement>;
			feDisplacementMap: ElementProps<SVGFEDisplacementMapElement>;
			feDistantLight: ElementProps<SVGFEDistantLightElement>;
			feDropShadow: ElementProps<SVGFEDropShadowElement>;
			feFlood: ElementProps<SVGFEFloodElement>;
			feFuncA: ElementProps<SVGFEFuncAElement>;
			feFuncB: ElementProps<SVGFEFuncBElement>;
			feFuncG: ElementProps<SVGFEFuncGElement>;
			feFuncR: ElementProps<SVGFEFuncRElement>;
			feGaussianBlur: ElementProps<SVGFEGaussianBlurElement>;
			feImage: ElementProps<SVGFEImageElement>;
			feMerge: ElementProps<SVGFEMergeElement>;
			feMergeNode: ElementProps<SVGFEMergeNodeElement>;
			feMorphology: ElementProps<SVGFEMorphologyElement>;
			feOffset: ElementProps<SVGFEOffsetElement>;
			fePointLight: ElementProps<SVGFEPointLightElement>;
			feSpecularLighting: ElementProps<SVGFESpecularLightingElement>;
			feSpotLight: ElementProps<SVGFESpotLightElement>;
			feTile: ElementProps<SVGFETileElement>;
			feTurbulence: ElementProps<SVGFETurbulenceElement>;
			filter: ElementProps<SVGFilterElement>;
			foreignObject: ElementProps<SVGForeignObjectElement>;
			g: ElementProps<SVGGElement>;
			image: ElementProps<SVGImageElement>;
			line: ElementProps<SVGLineElement>;
			linearGradient: ElementProps<SVGLinearGradientElement>;
			marker: ElementProps<SVGMarkerElement>;
			mask: ElementProps<SVGMaskElement>;
			metadata: ElementProps<SVGMetadataElement>;
			mpath: ElementProps<SVGMPathElement>;
			path: ElementProps<SVGPathElement>;
			pattern: ElementProps<SVGPatternElement>;
			polygon: ElementProps<SVGPolygonElement>;
			polyline: ElementProps<SVGPolylineElement>;
			radialGradient: ElementProps<SVGRadialGradientElement>;
			rect: ElementProps<SVGRectElement>;
			'svg:script': ElementProps<SVGScriptElement>;
			set: ElementProps<SVGSetElement>;
			stop: ElementProps<SVGStopElement>;
			'svg:style': ElementProps<SVGStyleElement>;
			svg: ElementProps<SVGSVGElement>;
			switch: ElementProps<SVGSwitchElement>;
			symbol: ElementProps<SVGSymbolElement>;
			text: ElementProps<SVGTextElement>;
			textPath: ElementProps<SVGTextPathElement>;
			'svg:title': ElementProps<SVGTitleElement>;
			tspan: ElementProps<SVGTSpanElement>;
			use: ElementProps<SVGUseElement>;
			view: ElementProps<SVGViewElement>;
		}
		interface MathMLIntrinsicElements {
			annotation: ElementProps<MathMLElement>;
			'annotation-xml': ElementProps<MathMLElement>;
			maction: ElementProps<MathMLElement>;
			math: ElementProps<MathMLElement>;
			merror: ElementProps<MathMLElement>;
			mfrac: ElementProps<MathMLElement>;
			mi: ElementProps<MathMLElement>;
			mmultiscripts: ElementProps<MathMLElement>;
			mn: ElementProps<MathMLElement>;
			mo: ElementProps<MathMLElement>;
			mover: ElementProps<MathMLElement>;
			mpadded: ElementProps<MathMLElement>;
			mphantom: ElementProps<MathMLElement>;
			mprescripts: ElementProps<MathMLElement>;
			mroot: ElementProps<MathMLElement>;
			mrow: ElementProps<MathMLElement>;
			ms: ElementProps<MathMLElement>;
			mspace: ElementProps<MathMLElement>;
			msqrt: ElementProps<MathMLElement>;
			mstyle: ElementProps<MathMLElement>;
			msub: ElementProps<MathMLElement>;
			msubsup: ElementProps<MathMLElement>;
			msup: ElementProps<MathMLElement>;
			mtable: ElementProps<MathMLElement>;
			mtd: ElementProps<MathMLElement>;
			mtext: ElementProps<MathMLElement>;
			mtr: ElementProps<MathMLElement>;
			munder: ElementProps<MathMLElement>;
			munderover: ElementProps<MathMLElement>;
			semantics: ElementProps<MathMLElement>;
		}

		type AllKnownTags = keyof HTMLIntrinsicElements | keyof SVGIntrinsicElements | keyof MathMLIntrinsicElements;

		type IsKnownTag<T extends string> = T extends AllKnownTags ? true : false;

		type IntrinsicElements = {
			[K in AllKnownTags]: K extends keyof HTMLIntrinsicElements
				? HTMLIntrinsicElements[K]
				: K extends keyof SVGIntrinsicElements
					? SVGIntrinsicElements[K]
					: K extends keyof MathMLIntrinsicElements
						? MathMLIntrinsicElements[K]
						: never;
		} & {
			[K in string as IsKnownTag<K> extends false ? K : never]: ElementProps<HTMLElement>;
		};

		type ElementClass = JSXFunction | Component<any>;
		type Element = Node;
	}
}
